#include <process.h>
#include <time.h>
#include <signal.h>
#include <stdlib.h>
#include <rapidxml_print.hpp>
//#include <base64.h>

#include "common.h"
#include "NetworkManager.h"
#include "ParamManager.h"
#include "TaskManager.h"
#include "NodeManager.h"
#include "ProcManager.h"
#include "ClockManager.h"
#include "Log.h"

#include <iostream>
using namespace std;
using namespace rapidxml;

#ifdef _WIN32
#ifdef _DEBUG
   #include <crtdbg.h>
   #undef THIS_FILE
   static char THIS_FILE[] = __FILE__;
   #define new       new( _NORMAL_BLOCK, __FILE__, __LINE__)
   #define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif
#endif


#include <direct.h>

// global used to catch a ctrl-c signal (extern defined in 'stbd_c.h')
bool _intHandler = false;

// called when 'ctrl-c' is caught
void intHandler(int dummy = 0) {
	// global variable set when 'ctrl-c' is caught
	_intHandler = true;
}


// used to launch a drone on a thread
unsigned __stdcall drone_thread(void *_params) {
	// launch the the task drone, should only exit for three reasons:
	// - task is complete
	// - signaled, by stbd_c or system is shutting down
	// - task drone crashed
//	system(".\\stbd_t.exe");

//	Sleep(10000);

    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );

	CreateProcess(NULL, ".\\stbd_t.exe", NULL, NULL, false, 0, NULL, NULL, &si, &pi);

    // Wait until child process exits.
    WaitForSingleObject( pi.hProcess, INFINITE );

    // Close process and thread handles. 
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );

	// end the thread
	_endthreadex(0);
	return 0;
}

bool launch_drone(Task *task, Log *log) {
//	return false;
	char data[DEFAULT_BUFLEN];
	
	// paranoid check
	if(task == NULL) {
		return false;
	}

	// if the task needs a drone
	if(task->isDrone == false) {
		// if the task is in the right state
		if((task->state == PENDING) || (task->state == ACTIVE)) {
			sprintf(data, "launching drone\r\n");	log->write(data);

			// launch a task drone
			task->drone_handle = (HANDLE)_beginthreadex(NULL, 0, &drone_thread, 0, 0, &(task->drone_id));

			// before storing for tracking purposes, check for error cases
			if(task->drone_handle == 0) {
				sprintf(data, "unable to start thread: %d\r\n", task->drone_id);	log->write(data);
				switch(errno) {
				case EAGAIN:
					log->write("errno: too many threads\r\n");
					break;
				case EINVAL:
					log->write("errno: the argument is invalid or the stack size is incorrect\r\n");
					break;
				case EACCES:
					log->write("errno: insufficient resources (such as memory)\r\n");
					break;
				case ENOMEM:
					log->write("errno: Not enough memory\r\n");
					break;
				default:
					sprintf(data, "errno: unknown (%d, %d)\r\n", errno, _doserrno);	log->write(data);
					break;
				}
				// might as well abort if this problem is occuring
				log->write("abort, unable to start drone\r\n");
			}
			else {
				// if we reached this point, everything went well
				return true;
			}
		}
	}

	// if everything didn't go well it must have failed
	return false;
}


int main(int argc, char *argv[]) {
	NetworkManager *network = new NetworkManager();
	TaskManager *tm = new TaskManager();
	ParamManager *pm = new ParamManager();
	NodeManager *nm = new NodeManager();
	Log *log = new Log("logs\\stbd_c.log");
	ProcManager *proc = new ProcManager(nm, tm, log);
	int cache_cpu = 0;
	int drones = 0;

	char *next = NULL;	// next string from the network
	char data[DEFAULT_BUFLEN] = { "" };	// form strings to send to the network

	// catch process interrupt, and save out state information
	signal(SIGINT, intHandler);
	//signal(SIGKILL, intHandler);

	ClockManager *clocks = new ClockManager();

	// number of seconds to wait before adjusting threads to balance cpu
	clocks->addCountdown("adjust_threads", 1, network->hReady);
	clocks->start("adjust_threads");
	clocks->addCountdown("server_status", 30, network->hReady);
//	clocks->addCountdown("server_status", 5 * 60, network->hReady);
	clocks->start("server_status");

#ifdef _DEBUG
	// display the cwd
	char cwd[DEFAULT_BUFLEN];
	_getcwd(cwd, DEFAULT_BUFLEN);
	sprintf(data, "cwd = '%s'\r\n", cwd);
	log->write(data);
#endif // _DEBUG


	// load available node modules
	nm->load();


	// load 'port' from the settings file
	TCHAR _port[DEFAULT_BUFLEN] = { "" };
	GetPrivateProfileString(
		TEXT("default"),
		TEXT("port"),
		TEXT("2244"),	// default value if not found
		_port,
		DEFAULT_BUFLEN,
		TEXT(".\\stbd_c.ini"));

	// load 'host' from the settings file
	TCHAR _host[DEFAULT_BUFLEN] = { "" };
	GetPrivateProfileString(
		TEXT("default"),
		TEXT("host"),
		TEXT("127.0.0.1"),	// default value if not found
		_host,
		DEFAULT_BUFLEN,
		TEXT(".\\stbd_c.ini"));


	// Open up a listen port for worker drones
	Connection *conn = network->add();
	conn->settings.set("isListen", "true");
	conn->settings.set("port", "2243");
	network->start(conn);


	// Establish connection to the server
	conn = network->add();
	sprintf(data, "_host '%s'\r\n", _host);	log->write(data);
	sprintf(data, "_port '%s'\r\n", _port);	log->write(data);
	conn->settings.set("host", _host);
	conn->settings.set("port", _port);
	conn->settings.set("isMain", "1");
	network->start(conn);


	// Obtain a task listing first thing
	strcpy(data, "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
	sprintf(data, "%s<packet>", data);
	sprintf(data, "%s<cmd type=\"request\">task_list</cmd>", data);
	sprintf(data, "%s<state>active</state>", data);
	sprintf(data, "%s</packet>\r\n", data);
//	sprintf(data, "cmd=task_list|state=active\r\n");
	if(!conn->write(data)) { log->write("sendBytes failed, task_list\r\n"); }

	// Continue forever, unless the computer is shutting down
	while(!_intHandler) {

		// wait forever until network activity
		network->waitForReady();

//		log->write("tick, todo: eliminate task track details... other than threads per task & drone tracking\r\n");


		// process the network activity
		if(network->isNew()) {
			while(conn = network->getNewConnection()) {
//				sprintf(data, "handling new network connection\r\n");	log->write(data);
			}
		}
		else if(network->isData()) {
			while(conn = network->getDataConnection()) {
				// Obtain the next network data to work with
				next = conn->getData();
#ifdef _DEBUG
//				sprintf(data, "[%d] Recv: %s\r\n", conn->socket, next);	log->write(data);
#endif // _DEBUG

				pm->init(next, "|");

/*				// Is this communication from a drone?
				if(!strcmp(conn->settings.get("isDrone"), "1")) {
					if(!strcmp(pm->get("cmd"), "thread_increase_reply")) {
						// ignore
					}
					else if(!strcmp(pm->get("cmd"), "thread_decrease_reply")) {
						// ignore
					}
					else if(!strcmp(pm->get("cmd"), "block_checkout")) {
						// forward to server
						sprintf(data, "%s\r\n", next);
						network->write("port", _port, data);
					}
					else if(!strcmp(pm->get("cmd"), "block_checkin")) {
						// forward to server
						sprintf(data, "%s\r\n", next);
						network->write("port", _port, data);
					}
					else {
						// see if the data parses successfully as xml
						char *text = new char[strlen(next) + 1];
						strcpy(text, next);

						xml_document<> doc;
						try {
							doc.parse<0>(text);
					
							// standard way to use rapidxml, though there will only ever be one packet
							if(doc.first_node("packet") && doc.first_node("packet")->first_node("cmd")) {	// validate
								xml_node<> *packet = doc.first_node("packet");
								xml_node<> *cmd = packet->first_node("cmd");
//								cout << "cmd: '" << cmd->value() << "'" << endl;
								if(!strcmp(cmd->value(), "status_set"))  {
									if(packet->first_node("drone")) {	// validate
										xml_node<> *drone = packet->first_node("drone");
										if(drone->first_node("task_id") && drone->first_node("threads")) {	// validate
											Task *task = tm->get(drone->first_node("task_id")->value());
											if(task == NULL) {
												log->write("task_get failed, status_set\n");
												break;
											}
											// remember the provided threads setting
											task->status.set("threads", drone->first_node("threads")->value());
											task->threads = atoi(drone->first_node("threads")->value());

											char index_data[DEFAULT_BUFLEN];	strcpy(index_data, "");
											for(xml_node<> *block = drone->first_node("block"); block; block = block->next_sibling("block")) {
												if(block->first_node("index")) {
													char *index = block->first_node("index")->value();
													
													for(xml_node<> *detail = block->first_node(); detail; detail = detail->next_sibling()) {
														// we should remember multiple blocks, but we only remember one for now
														if(!strcmp(detail->name(), "pending")) {
															sprintf(data, "%s_%s_pending", task->id, index);
															task->status.set(data, detail->value());
														}
														else if(!strcmp(detail->name(), "active")) {
															sprintf(data, "%s_%s_active", task->id, index);
															task->status.set(data, detail->value());
														}
														else if(!strcmp(detail->name(), "complete")) {
															sprintf(data, "%s_%s_complete", task->id, index);
															task->status.set(data, detail->value());
														}
														else if(!strcmp(detail->name(), "threads")) {
															sprintf(data, "%s_%s_threads", task->id, index);
															task->status.set(data, detail->value());
															task->threads = atoi(detail->value());
														}
													}

													// remember this index
													if(strlen(index_data) == 0) {				
														sprintf(index_data, "%s=%s", task->id, index);
													}
													else {
														sprintf(index_data, "%s|%s=%s", index_data, task->id, index);
													}
													task->status.set("index", index_data);
												}
											}
										}
									}
								}
							}
						} catch(rapidxml::parse_error code) {
							// otherwise, just forward on drone communication to the server
							sprintf(data, "%s\r\n", next);
							network->write("port", _port, data);
						}

						// cleanup allocated resources
						delete text;

					}
				}
*/
				// Is it a new drone?
				if(!strcmp(pm->get("cmd"), "ident_drone")) {					
					sprintf(data, "drone has checked in\r\n");	log->write(data);

					// flag the connection as a task drone
					conn->settings.set("isDrone", "1");
					sprintf(data, "conn isDrone=1\r\n");	log->write(data);

					// set task id with default value, non-set value, should only occur with an unexpected drone
					conn->settings.set("task_id", "0");
					sprintf(data, "conn task_id=0\r\n");	log->write(data);

					// Is there a task ready?  There should be.  The drone was started for a task.
					Task *task = tm->reset();
					while(task) {
						if(!task->isDroneAssigned) {
							if((task->state == PENDING) || (task->state == ACTIVE)) {
								sprintf(data, "task found, conn task_id='%s'\r\n", task->id);	log->write(data);
								// associate this drone with this task
								conn->settings.set("task_id", task->id);
								task->isDroneAssigned = true;
	
								// request detail related to task
								sprintf(data, "cmd=task_detail|id=%s\r\n", task->id);
								network->write("port", _port, data);
								break;
							}
						}
						task = tm->getNext();
					}
				}

				// Drone communication
				else if(!strcmp(pm->get("cmd"), "thread_increase_reply")) {
//					printf("thread_increase_reply\n");
					if(!strcmp(conn->settings.get("isDrone"), "1")) {	// if has identified as a drone
						// ignore
					}
				}
				else if(!strcmp(pm->get("cmd"), "thread_decrease_reply")) {
//					printf("thread_decrease_reply\n");
					if(!strcmp(conn->settings.get("isDrone"), "1")) {	// if has identified as a drone
						// ignore
					}
				}
				else if(!strcmp(pm->get("cmd"), "block_checkout")) {
//					printf("block_checkout\n");
					if(!strcmp(conn->settings.get("isDrone"), "1")) {	// if has identified as a drone
						// forward to server
						sprintf(data, "%s\r\n", next);
						network->write("port", _port, data);
					}
				}
				else if(!strcmp(pm->get("cmd"), "block_checkin")) {
//					printf("block_checkin\n");
					if(!strcmp(conn->settings.get("isDrone"), "1")) {	// if has identified as a drone
						// forward to server
						sprintf(data, "%s\r\n", next);
						network->write("port", _port, data);
					}
				}

				// Could it be a request for statistics?  Used for gui stat display.
				else if(!strcmp(pm->get("cmd"), "status_get")) {
					sprintf(data, "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\r\n");					conn->write(data);
					sprintf(data, "<response cmd=\"status_get\">\r\n");										conn->write(data);
					sprintf(data, "\t<cpu>%d</cpu>\r\n", proc->cpu->getPercent());							conn->write(data);
					sprintf(data, "\t<tasklist>\r\n");														conn->write(data);
					Task *task = tm->head;
					while(task) {
						sprintf(data, "\t<task>\r\n");														conn->write(data);
						sprintf(data, "\t\t<id>%s</id>\r\n", task->id);										conn->write(data);
						sprintf(data, "\t\t<state>%d</state>\r\n", task->state);							conn->write(data);
						sprintf(data, "\t\t<pending>%s</pending>\r\n", task->pm.get("total"));				conn->write(data);
						sprintf(data, "\t\t<complete>%s</complete>\r\n", task->pm.get("complete"));			conn->write(data);
						sprintf(data, "\t</task>\r\n");														conn->write(data);
						task = task->next;
					}
					sprintf(data, "\t</tasklist>\r\n");														conn->write(data);
					sprintf(data, "\t<dronelist>\r\n");														conn->write(data);
					Connection *slider = network->cm->head;
					while(slider) {
						if(!strcmp(slider->get("isMain"), "1")) {
							// ignore
						}
						else if(!strcmp(slider->get("isListen"), "true")) {
							// ignore
						}
						else if(!strcmp(slider->get("isDrone"), "1")) {
							Task *task = tm->get(slider->get("task_id"));
							
							if(!task) {
								sprintf(data, "\t\t<drone>\r\n");											conn->write(data);
								sprintf(data, "\t\t</drone>\r\n");											conn->write(data);
							}
							else if(!task->isLoaded) {
								sprintf(data, "\t\t<drone>\r\n");											conn->write(data);
								sprintf(data, "\t\t</drone>\r\n");											conn->write(data);
							}
							else {
								sprintf(data, "\t\t<drone>\r\n");											conn->write(data);
								sprintf(data, "\t\t\t<task_id>%s</task_id>\r\n", slider->get("task_id"));	conn->write(data);
								sprintf(data, "\t\t\t<todo>%s</todo>\r\n", task->pm.get("todo"));			conn->write(data);
								sprintf(data, "\t\t\t<action>%s</action>\r\n", task->pm.get("action"));		conn->write(data);
								sprintf(data, "\t\t\t<threads>%s</threads>\r\n", "99");						conn->write(data);
								sprintf(data, "\t\t\t<timestamp>%lld</timestamp>\r\n", (long long)task->timestamp);			conn->write(data);
								
								char tmp[DEFAULT_BUFLEN];
								ParamManager indexes;
//								printf("INDEX: '%s'\n", task->status.get("index"));
								indexes.setOverwrite(false);
								indexes.init(task->status.get("index"), "|");
								indexes.reset();
								while(Param *_next = indexes.getNext()) {
//									printf("next {\n");
//									printf("  %s = %s\n}\n", _next->data, _next->value);
									sprintf(data, "\t\t\t<block>\r\n");												conn->write(data);
									sprintf(data, "\t\t\t\t<index>%s</index>\r\n", _next->value);					conn->write(data);
									sprintf(tmp, "%s_%s_pending", _next->data, _next->value);
									sprintf(data, "\t\t\t\t<pending>%s</pending>\r\n", task->status.get(tmp));		conn->write(data);
									sprintf(tmp, "%s_%s_active", _next->data, _next->value);
									sprintf(data, "\t\t\t\t<active>%s</active>\r\n", task->status.get(tmp));		conn->write(data);
									sprintf(tmp, "%s_%s_complete", _next->data, _next->value);
									sprintf(data, "\t\t\t\t<complete>%s</complete>\r\n", task->status.get(tmp));	conn->write(data);
									sprintf(tmp, "%s_%s_threads", _next->data, _next->value);
									sprintf(data, "\t\t\t\t<threads>%s</threads>\r\n", task->status.get(tmp));		conn->write(data);
									sprintf(data, "\t\t\t</block>\r\n");											conn->write(data);
								}

								sprintf(data, "\t\t</drone>\r\n");											conn->write(data);
							}
						}
						else {
							sprintf(data, "\t\t<unknown></unknown>\r\n");									conn->write(data);
						}

						slider = slider->next;
					}
					sprintf(data, "\t</dronelist>\r\n");													conn->write(data);
					sprintf(data, "</response>\r\n");														conn->write(data);
				}

				// Must be communication with the server
				else if(!strcmp(pm->get("cmd"), "task_list_reply")) {
					// clean up our task list, and let it be rebuilt
//					tm.clear();
				}
				else if(!strcmp(pm->get("cmd"), "task_list_item")) {
					// ensure the task is not already being tracked
					char *id = pm->get("id");
					Task *task = tm->get(id);
					if(task == NULL) {
						//task = tm->add(pm->get("id"), (State) atoi(pm->get("state")));
						task = tm->add(pm->get("id"), PENDING);	// always pending until details are loaded
						if(task == NULL) {
							log->write("task_add failed, task_list_item\r\n");
							// move to next string to process
							conn->pop();
							// move to next loop iteration
							continue;
						}	// if task not found, skip the following steps

						// set task parameter
						task->pm.set("todo", pm->get("todo"));
						task->pm.set("priority", pm->get("priority"));
					}

					// if task is active, go ahead and obtain detail (this usually occurs when a task starts)
					if(!strcmp(pm->get("state"), "1")) {
						task->state = (State) atoi(pm->get("state"));

						// launch a task drone
//						launch_drone(task, log);
					}
					// if task is complete
					else if(!strcmp(pm->get("state"), "2")) {
						// update the state of the task
						task->state = (State) atoi(pm->get("state"));

						// task is complete, no need to track it
						tm->del(task->id);

						// forward the data to the drone
//						sprintf(data, "%s\r\n", next);
//						network->write("task_id", pm->get("id"), data);
					}
				}
				else if(!strcmp(pm->get("cmd"), "task_get_reply")) {
					// ensure the task is not already being tracked
					char *id = pm->get("id");
					Task *task = tm->get(id);
					if(task == NULL) {
						task = tm->add(pm->get("id"), PENDING);
						if(task == NULL) {
							log->write("task_add failed, task_get_reply\r\n");
							// move to next string to process
							conn->pop();
							// move to next loop iteration
							continue;
						}	// if task not found, skip the following steps
					}
					else {
						// update the state of the task
						task->state = (State) atoi(pm->get("state"));

						// forward the data to the drone
						sprintf(data, "%s\r\n", next);
						network->write("task_id", pm->get("id"), data);
					}

					// set task parameter
					task->pm.set("todo", pm->get("todo"));
					task->pm.set("priority", pm->get("priority"));

					task->pm.set("complete", pm->get("complete"));
					task->pm.set("total", pm->get("total"));
				}
				else if(!strcmp(pm->get("cmd"), "task_detail_reply")) {
					Task *task = tm->get(pm->get("id"));
					if(task == NULL) {
						log->write("task_get failed, task_detail_reply\n");
						// move to next string to process
						conn->pop();
						// move to next loop iteration
						continue;
					}	// if task not found, skip the following steps

					// associate the detail with the stored task
					task->num_details = atoi(pm->get("count"));

					// delete all the current details, as we are about to be re-told
					task->pm.clear();

					// forward the data to the drone
					sprintf(data, "%s\r\n", next);
					network->write("task_id", pm->get("id"), data);
				}
				else if(!strcmp(pm->get("cmd"), "task_detail_item")) {
					Task *task = tm->get(pm->get("id"));
					if(task == NULL) {
						log->write("task_get failed, task_detail_item\r\n");
						// move to next string to process
						conn->pop();
						// move to next loop iteration
						continue;
					}	// if task not found, skip the following steps

					// associate the detail with the stored task
					task->pm.set(pm->get("data"), pm->get("value"));

					// if this was our last detail, look up the state
					int count = 0;
					Param *slider = task->pm.head;
					while(slider != NULL) {
						count ++;
						slider = slider->next;
					}
					//printf("testing %d / %d\n", count, task->num_details);
					if(count == task->num_details) {
						// set the loaded flag to true
						task->isLoaded = true;

						// obtain state with a 'task_get' command
						sprintf(data, "cmd=task_get|id=%s\r\n", pm->get("id"));
						if(!conn->write(data)) { log->write("sendBytes failed, task_get\r\n"); }

						// also, reset the number of threads currently allocated to todos
						// this new task may be more resource intensive
//						proc->cpu->params.threads_allowed = 10;
					}

					// forward the data to the drone
					sprintf(data, "%s\r\n", next);
					network->write("task_id", pm->get("id"), data);
				}
				else if(!strcmp(pm->get("cmd"), "notice")) {
	//				printf("Notice: %s, %s\r\n", pm->get("id"), pm->get("info"));

					if(!strcmp(pm->get("info"), "1")) {
						// a task has begun
#ifdef _DEBUG
						sprintf(data, "[Notice] 1: (id=%s) Task is active: %s\r\n", pm->get("id"), pm->get("todo"));	log->write(data);
#endif // _DEBUG

						// a new task has been added, get basic task info
						sprintf(data, "cmd=task_get|id=%s\r\n", pm->get("id"));
						if(!conn->write(data)) { log->write("sendBytes failed, task_get after notice_1\r\n"); }

						// load up detail related to task
//						sprintf(data, "cmd=task_detail|id=%s\r\n", pm->get("id"));
//						if(!conn->write(data)) { printf("sendBytes failed\n"); }
					}
					else if(!strcmp(pm->get("info"), "2")) {
						// a task has finished
#ifdef _DEBUG
						sprintf(data, "[Notice] 2: (id=%s) Task is complete\r\n", pm->get("id"));	log->write(data);
#endif // _DEBUG

						Task *task = tm->get(pm->get("id"));
						if(task == NULL) {
							log->write("task_get failed, info_2\r\n");
							// move to next string to process
							conn->pop();
							// move to next loop iteration
							continue;
						}	// if task not found, skip the following steps

						// flag that the task has been deleted
						task->state = COMPLETE;
						task->isShutdown = true;
					}

					// forward the data to the drone
					sprintf(data, "%s\r\n", next);
					network->write("task_id", pm->get("id"), data);
				}
				else if(!strcmp(pm->get("cmd"), "block_checkout_reply")) {
					// ignore, stbd_t handles blocks

					// forward the data to the drone
					sprintf(data, "%s\r\n", next);
					network->write("task_id", pm->get("id"), data);
				}
				else if(!strcmp(pm->get("cmd"), "block_checkin_reply")) {
					// ignore, stbd_t handles blocks

					// forward the data to the drone
					sprintf(data, "%s\r\n", next);
					network->write("task_id", pm->get("id"), data);
				
				}
				else {
					// if able to base64 decode successfully, then this may be xml
					char text[DEFAULT_BUFLEN];

					printf("xml parse: '%s'\n", next);
					strcpy(text, next);
					xml_document<> doc;
					try {
						doc.parse<0>(text);
					
						// standard way to use rapidxml, though there will only ever be one packet
						if(doc.first_node("packet") && doc.first_node("packet")->first_node("cmd")) {	// validate
							xml_node<> *packet = doc.first_node("packet");
							xml_node<> *cmd = packet->first_node("cmd");

							cout << "(XML) cmd = '" << cmd->value() << "'" << endl;
				

// Is this communication from a drone?
if(!strcmp(conn->settings.get("isDrone"), "1")) {
	if(!strcmp(cmd->value(), "thread_increase_reply"))  {
		// ignore
	}
	else if(!strcmp(cmd->value(), "thread_decrease_reply"))  {
		// ignore
	}
	else if(!strcmp(cmd->value(), "block_checkout") && !strcmp( (cmd->first_attribute("type", 0, false))->value(), "request") )  {
		// forward to the server
		sprintf(data, "%s\r\n", next);
		network->write("port", _port, data);
	}
	else if(!strcmp(cmd->value(), "block_checkin") && !strcmp( (cmd->first_attribute("type", 0, false))->value(), "request") )  {
		// forward to the server
		sprintf(data, "%s\r\n", next);
		network->write("port", _port, data);
	}
	else if(!strcmp(cmd->value(), "status_set") && !strcmp( (cmd->first_attribute("type", 0, false))->value(), "request") )  {
		if(packet->first_node("drone")) {	// validate
			xml_node<> *drone = packet->first_node("drone");
			if(drone->first_node("task_id") && drone->first_node("threads")) {	// validate
				Task *task = tm->get(drone->first_node("task_id")->value());
				if(task == NULL) {
					log->write("task_get failed, status_set\n");
					break;
				}
				// remember the provided threads setting
				task->status.set("threads", drone->first_node("threads")->value());
				task->threads = atoi(drone->first_node("threads")->value());

				char index_data[DEFAULT_BUFLEN];	strcpy(index_data, "");
				for(xml_node<> *block = drone->first_node("block"); block; block = block->next_sibling("block")) {
					if(block->first_node("index")) {
						char *index = block->first_node("index")->value();
													
						for(xml_node<> *detail = block->first_node(); detail; detail = detail->next_sibling()) {
							// we should remember multiple blocks, but we only remember one for now
							if(!strcmp(detail->name(), "pending")) {
								sprintf(data, "%s_%s_pending", task->id, index);
								task->status.set(data, detail->value());
							}
							else if(!strcmp(detail->name(), "active")) {
								sprintf(data, "%s_%s_active", task->id, index);
								task->status.set(data, detail->value());
							}
							else if(!strcmp(detail->name(), "complete")) {
								sprintf(data, "%s_%s_complete", task->id, index);
								task->status.set(data, detail->value());
							}
							else if(!strcmp(detail->name(), "threads")) {
								sprintf(data, "%s_%s_threads", task->id, index);
								task->status.set(data, detail->value());
								task->threads = atoi(detail->value());
							}
						}

						// remember this index
						if(strlen(index_data) == 0) {				
							sprintf(index_data, "%s=%s", task->id, index);
						}
						else {
							sprintf(index_data, "%s|%s=%s", index_data, task->id, index);
						}
						task->status.set("index", index_data);
					}
				}
			}
		}
	}
	else {
		// otherwise, just forward on drone communication to the server
//		sprintf(data, "%s\r\n", next);
//		network->write("port", _port, data);
	}
}
// Is it a new drone?
else if(!strcmp(cmd->value(), "ident_drone") && !strcmp( (cmd->first_attribute("type", 0, false))->value(), "request") )  {
//	printf("xml: ident_drone\n");
	sprintf(data, "drone has checked in\r\n");	log->write(data);

	// flag the connection as a task drone
	conn->settings.set("isDrone", "1");
	sprintf(data, "conn isDrone=1\r\n");	log->write(data);

	// set task id with default value, non-set value, should only occur with an unexpected drone
	conn->settings.set("task_id", "0");
	sprintf(data, "conn task_id=0\r\n");	log->write(data);

	// Is there a task ready?  There should be.  The drone was started for a task.
	Task *task = tm->reset();
	while(task) {
		if(!task->isDroneAssigned) {
			if((task->state == PENDING) || (task->state == ACTIVE)) {
				sprintf(data, "task found, conn task_id='%s'\r\n", task->id);	log->write(data);
				// associate this drone with this task
				conn->settings.set("task_id", task->id);
				task->isDroneAssigned = true;
	
				// request detail related to task
				strcpy(data, "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
				sprintf(data, "%s<packet>", data);
				sprintf(data, "%s<cmd type=\"request\">task_detail</cmd>", data);
				sprintf(data, "%s<id>%s</id>", data, task->id);
				sprintf(data, "%s</packet>\r\n", data);
//				sprintf(data, "cmd=task_detail|id=%s\r\n", task->id);
				network->write("port", _port, data);
				break;
			}
		}
		task = tm->getNext();
	}
}

// Must be communication with the server
else if(!strcmp(cmd->value(), "task_list") && !strcmp( (cmd->first_attribute("type", 0, false))->value(), "response") ) {
//	printf("xml: task_list\n");
	if(packet->first_node("task_list")) {
		xml_node<> *task_list = packet->first_node("task_list");
		for(xml_node<> *xtask = task_list->first_node(); xtask; xtask = xtask->next_sibling()) {
			if(xtask->first_node("id")) {
				Task *task = tm->get( xtask->first_node("id")->value() );
				if(task == NULL) {
					task = tm->add(xtask->first_node("id")->value(), PENDING);	// always pending until details are loaded
					if(task == NULL) {
						log->write("task_add failed, task_list\r\n");
						// move to next string to process
						conn->pop();
						// move to next loop iteration
						continue;
					}	// if task not found, skip the following steps
				}
				printf("(task_list)->task {\n");
				for(xml_node<> *xitem = xtask->first_node(); xitem; xitem = xitem->next_sibling()) {
					printf("  %s = %s\n", xitem->name(), xitem->value());

					// remember task parameter
					if(!stricmp(xitem->name(), "todo")) {
						task->pm.set(xitem->name(), xitem->value());
					}
					else if(!stricmp(xitem->name(), "priority")) {
						task->pm.set(xitem->name(), xitem->value());
					}
					//task->pm.set(xitem->name(), xitem->value());

					// special steps based on the state value
					if(!stricmp(xitem->name(), "state")) {
						if(!stricmp(xitem->value(), "1")) {
							// if task is active, go ahead and obtain detail (this usually occurs when a task starts)
							task->state = (State) atoi(xitem->value());
						}
						// if task is complete
						else if(!stricmp(xitem->value(), "2")) {
							// update the state of the task
							task->state = (State) atoi(xitem->value());

							// task is complete, no need to track it
							tm->del(task->id);
						}
					}

				}
				printf("}\n");
			}
		}
	}
}
else if(!strcmp(cmd->value(), "task_detail") && !strcmp( (cmd->first_attribute("type", 0, false))->value(), "response") )  {
	if(packet->first_node("task_detail") && packet->first_node("id")) {
		Task *task = tm->get( packet->first_node("id")->value() );
		if(task == NULL) {
			log->write("task_get failed, task_detail_reply\n");
			// move to next string to process
			conn->pop();
			// move to next loop iteration
			continue;
		}	// if task not found, skip the following steps

		// delete all the current details, as we are about to be re-told
		task->pm.clear();

		xml_node<> *xtask_detail = packet->first_node("task_detail");
		printf("(task_detail)->detail {\n");
		for(xml_node<> *xdetail = xtask_detail->first_node(); xdetail; xdetail = xdetail->next_sibling()) {
			// remember the detail
			printf("  %s = %s\n", xdetail->name(), xdetail->value());
			task->pm.set(xdetail->name(), xdetail->value());
		}
		printf("}\n");

		// store off number of details
		task->num_details = task->pm.getCount();	// todo: remove, does nothing with xml implementation

		// set the loaded flag to true
		task->isLoaded = true;

		// forward the data to the drone
		sprintf(data, "%s\r\n", next);
		network->write("task_id", packet->first_node("id")->value(), data);

		// obtain state with a 'task_get' command
		strcpy(data, "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
		sprintf(data, "%s<packet>", data);
		sprintf(data, "%s<cmd type='request'>task_get</cmd>", data);
		sprintf(data, "%s<id>%s</id>", data, packet->first_node("id")->value());
		sprintf(data, "%s</packet>\r\n", data);
//		sprintf(data, "cmd=task_get|id=%s\r\n", packet->first_node("id")->value());
		if(!conn->write(data)) { log->write("sendBytes failed, task_get\r\n"); }
	}
}
else if(!strcmp(cmd->value(), "task_get") && !strcmp( (cmd->first_attribute("type", 0, false))->value(), "response") )  {
	if(packet->first_node("task") && packet->first_node("id")) {	// validate
		xml_node<> *xtask = packet->first_node("task");
		Task *task = tm->get( packet->first_node("id")->value() );
		if(task == NULL) {
			log->write("task_get failed, task_detail_reply\n");
			// move to next string to process
			conn->pop();
			// move to next loop iteration
			continue;
		}	// if task not found, skip the following steps
		else {
			// update the state of the task
			task->state = (State) atoi(xtask->first_node("state")->value());

			// forward the data to the drone
			sprintf(data, "%s\r\n", next);
			network->write("task_id", packet->first_node("id")->value(), data);
		}

		for(xml_node<> *xdetail = xtask->first_node(); xdetail; xdetail = xdetail->next_sibling()) {
			// set task parameter
			task->pm.set(xdetail->name(), xdetail->value());
		}
	}
}
else if(!strcmp(cmd->value(), "block_checkout") && !strcmp( (cmd->first_attribute("type", 0, false))->value(), "response") )  {
//	printf("xml: block_checkout, response\n");
	if(packet->first_node("id")) {	// validate		
		// forward the data to the drone
		sprintf(data, "%s\r\n", next);
		network->write("task_id", packet->first_node("id")->value(), data);
	}
	if(cmd->first_attribute("status", 0, false) && packet->first_node("id")) {	// validate
		Task *task = tm->get( packet->first_node("id")->value() );
		if(task) {
			if(!stricmp( cmd->first_attribute("status", 0, false)->value(), "0")) {
				if(packet->first_node("block")->first_node("index")
					&& packet->first_node("block")->first_node("size")) {	// validate

					xml_node<> *xblock = packet->first_node("block");
//					printf("remembering block: %s, %d\n", xblock->first_node("index")->value(), atoi(xblock->first_node("size")->value()));

					// remember block
//					task->bm.add(xblock->first_node("index")->value(), atoi(xblock->first_node("size")->value()));
//					task->isCheckout = false;
				}
			}
			// task is either busy, or the task_id is unknown
			else {
				printf("(block_checkout) busy\n");
//				task->isCheckout = false;	
//				task->t_curr = time(NULL);
//				task->isBusy = true;
			}
		}
	}
}
else if(!strcmp(cmd->first_attribute("type", 0, false)->value(), "response")
	&& !strcmp(cmd->value(), "block_checkin"))  {
//	printf("xml: block_checkin, response\n");
	
	if(packet->first_node("id")) {	// validate		
		// forward the data to the drone
		sprintf(data, "%s\r\n", next);
		network->write("task_id", packet->first_node("id")->value(), data);
	}
}
else if(!strcmp(cmd->first_attribute("type", 0, false)->value(), "notice")
	&& !strcmp(cmd->value(), "task_start")
	&& packet->first_node("id")
	&& packet->first_node("todo")
	)  {
	printf("task_start (%s)\n", packet->first_node("id")->value());

	Task *task = tm->get( packet->first_node("id")->value() );
	if(task == NULL) {
		tm->add(packet->first_node("id")->value(), PENDING);
		task = tm->get( packet->first_node("id")->value() );
	}

	// obtain state with a 'task_get' command
	strcpy(data, "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
	sprintf(data, "%s<packet>", data);
	sprintf(data, "%s<cmd type=\"request\">task_get</cmd>", data);
	sprintf(data, "%s<id>%s</id>", data, packet->first_node("id")->value());
	sprintf(data, "%s</packet>\r\n", data);
	printf("task_start data:\n%s", data);
	conn->write(data);
}
else if(!strcmp(cmd->first_attribute("type", 0, false)->value(), "notice")
	&& !strcmp(cmd->value(), "task_complete"))  {

	if(packet->first_node("id")) {	// validate
		Task *task = tm->get( packet->first_node("id")->value() );
		if(task == NULL) {
			log->write("task_get failed, task_detail_reply\n");
			// move to next string to process
			conn->pop();
			// move to next loop iteration
			continue;
		}	// if task not found, skip the following steps
		else {
			// flag that the task has been deleted
			task->state = COMPLETE;
			task->isShutdown = true;

			// forward the data to the drone
			sprintf(data, "%s\r\n", next);
			network->write("task_id", packet->first_node("id")->value(), data);
		}
	}
}
else {
	// otherwise, received data is not valid protocol
	sprintf(data, "[XML Invalid Protocol] data ignored: '%s'\r\n", next);	log->write(data);
}

						}
					} catch(rapidxml::parse_error code) {
						sprintf(data, "[Non-XML Invalid Protocol] data ignored: '%s'\r\n", next);	log->write(data);
					}

				}

				// move to next string to process
				conn->bm->pop();
			}
		}
		else if(network->isDel()) {
			// Occurs in two situations:
			// 1. The server has dropped the connection.  Could try reconnecting instead, for now do nothing.
			// 2. A drone has dropped its connection

			while(conn = network->getDelConnection()) {
				// Is the dropped connection a drone?
				if(!strcmp(conn->settings.get("isDrone"), "1")) {
					// adjust task to indicate there is no drone
					Task *task = tm->get(conn->settings.get("task_id"));
					if(task) {
						task->isDrone = task->isDroneAssigned = false;

						// Did the drone drop in a planned way?
						if(task->state != COMPLETE) {
							// Unplanned, start up a new drone to replace this crashed one
							sprintf(data, "drone crashed\r\n");	log->write(data);
							printf("** Drone crashed, launching recovery drone after 30 second delay.\r\n");
//							Sleep(30 * 1000);
//							launch_drone(task, log);
						}
					}

					// subtrack for the # of active drones
					drones --;
				}
				// Is the dropped connection the connection to the server?
				else if(!strcmp(conn->settings.get("port"), _port)) {
					if(!strcmp(conn->settings.get("isMain"), "1")) {
						// We could try reconnecting, exit the program for now. -- abort by simulating a Ctrl-C --
						_intHandler = true;
					}
				}
				// Is the dropped connection on the drone port?
				else if(!strcmp(conn->settings.get("port"), "2243")) {
					// could be a drone attempt which did not identify yet
					// who knows, ignore the dropped connection
				}
				else {
					// ignore the dropped connection
					printf("ignoring dropped connection\r\n");
					if(conn->settings.isSet("host")) printf(" host: %s\r\n", conn->settings.get("host"));
					if(conn->settings.isSet("port")) printf(" port: %s\r\n", conn->settings.get("port"));
				}

				// free up the resources of the dropped connection
				network->del(conn);
			}
		}

		// Network activity has gone idle

		// At this point, we have a gap in network chatter.  We will now request a
		// block to process using preference based on a priority algorithm.  This
		// method does mean that while there is a lot of network traffic, no blocks
		// will be processed.  This is ok because the TBD Protocol has little traffic.

		// mark the time, used to break out of thread launching to make sure to watch network too
//		SYSTEMTIME st;
//		GetLocalTime(&st);
//		WORD t_start = st.wMilliseconds;
		ULONGLONG t_start = GetTickCount64();
//		sprintf(data, "%llu\r\n", t_start);
//		log->write(data);

		// update stats of threads of all tasks
//		proc->update();
#ifdef _DEBUG
//		proc->dump();
#endif // _DEBUG


		// If we are allowed another drone, launch one now
		if(drones < 5) {
			Task *task = tm->reset();
			while(task) {
				if(!task->isDrone) {
					if((task->state == PENDING) || (task->state == ACTIVE)) {
						launch_drone(task, log);
						task->isDrone = true;
						drones ++;
						break;	// start another drone later, if we are allowed more
					}
				}
				task = tm->getNext();
			}
		}

		// Has a task been flagged for delete?  Then delete.
		Task *task = tm->reset();
		while(task) {
			if(task->isShutdown) {
				// mark the state as complete
				task->state = COMPLETE;				// deleted so quickly, this can be erased

				// delete this task
				printf("deleting %s\n", task->id);
				char *tmp = new char[strlen(task->id) + 1];
				strcpy(tmp, task->id);
				tm->del(tmp);
				delete[] tmp;

				// restart the scan for tasks in shutdown state
				task = tm->reset();
				continue;	// in order to skip the task advancing command, which may be invalid
			}

			task = tm->getNext();
		}		

		// Do we have an active task to work on?
		if(proc->isReadyTask()) {
			// enable watching the cpu
			proc->cpu->start();
		}
		else {
			// pause watching the cpu
			proc->cpu->pause();
		}


		// Has our countdown to the next thread adjustment finished?
		if(clocks->isComplete("adjust_threads")) {
			// Do we have an active task to work on?
			if(proc->isReadyTask()) {
				// cpu is available, increase the thread count
				if(proc->isReadyCPU()) {
					// obtain the next todo who gets another thread
					char *task_id = proc->getAddThread(10);
//						printf("thread_increase: %s\n", task_id);
					if(task_id) {
						Task *task = tm->get(task_id);
//						if(task->threads < 10) {	// not needed, getAddThread does this check
							task->threads ++;
							network->write("task_id", task_id, "cmd=thread_increase|count=1\r\n");
//						}
					}
				}
				// cpu is too high, lower the thread count
				else {
					int min = 1;
					// obtain the next todo who looses a thread
					char *task_id = proc->getDelThread(0);
//						printf("thread_decrease: %s\n", task_id);
					if(task_id) {
						Task *task = tm->get(task_id);
//						if(task->threads > 1) {		// getDelThread should be performing this check
							task->threads --;
							network->write("task_id", task_id, "cmd=thread_decrease|count=1\r\n");
//						}
					}
				}
			}

			// reset for the next thread adjustment
			clocks->reset("adjust_threads");
			clocks->start("adjust_threads");
		}

		// Has our countdown to the next thread adjustment finished?
		if(clocks->isComplete("server_status")) {
			Task *task = tm->reset();
			while(task) {
				if((task->state == ACTIVE) && task->isLoaded) {
					// a new task has been added, get basic task info
					sprintf(data, "cmd=task_get|id=%s\r\n", task->id);
					network->write("port", "2244", data);
				}

				task = tm->getNext();
			}

			// reset for the next thread adjustment
			clocks->reset("server_status");
			clocks->start("server_status");
		}

		// Catch a Ctrl-C interrupt
		if(_intHandler) {
			break;
		}
	}	// end while(!_intHandler)

#ifdef _DEBUG
	tm->dump();
	log->write("Exiting.\r\n");
#endif // _DEBUG

	// free up memory
	delete log;
	delete clocks;
	delete proc;
	delete nm;
	delete pm;
	delete tm;
	delete network;

#ifdef _WIN32
#ifdef _DEBUG
	// Check for Memory Leaks
	_CrtDumpMemoryLeaks();
#endif
#endif
}